package com.caius.xiaohashu.comment.biz.service.Impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.RandomUtil;
import com.caius.framework.biz.context.holder.LoginUserContextHolder;
import com.caius.framework.common.constant.DateConstants;
import com.caius.framework.common.exception.BizException;
import com.caius.framework.common.response.PageResponse;
import com.caius.framework.common.response.Response;
import com.caius.framework.common.util.DateUtils;
import com.caius.framework.common.util.JsonUtils;
import com.caius.xiaohashu.comment.biz.constant.MQConstants;
import com.caius.xiaohashu.comment.biz.constant.RedisKeyConstants;
import com.caius.xiaohashu.comment.biz.domain.dataobject.CommentDO;
import com.caius.xiaohashu.comment.biz.domain.dataobject.CommentLikeDO;
import com.caius.xiaohashu.comment.biz.domain.mapper.CommentDOMapper;
import com.caius.xiaohashu.comment.biz.domain.mapper.CommentLikeDOMapper;
import com.caius.xiaohashu.comment.biz.domain.mapper.NoteCountDOMapper;
import com.caius.xiaohashu.comment.biz.enums.*;
import com.caius.xiaohashu.comment.biz.model.dto.LikeUnlikeCommentMqDTO;
import com.caius.xiaohashu.comment.biz.model.dto.PublishCommentMqDTO;
import com.caius.xiaohashu.comment.biz.model.vo.*;
import com.caius.xiaohashu.comment.biz.retry.SendMqRetryHelper;
import com.caius.xiaohashu.comment.biz.rpc.DistributedIdGeneratorRpcService;
import com.caius.xiaohashu.comment.biz.rpc.KeyValueRpcService;
import com.caius.xiaohashu.comment.biz.rpc.UserRcpService;
import com.caius.xiaohashu.comment.biz.service.CommentService;
import com.caius.xiaohashu.kv.dto.req.FindCommentContentReqDTO;
import com.caius.xiaohashu.kv.dto.resp.FindCommentContentRspDTO;
import com.caius.xiaohashu.user.dto.resp.FindUserByIdRspDTO;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.util.Strings;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.core.io.ClassPathResource;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

import javax.crypto.spec.OAEPParameterSpec;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author Caius
 * @description
 * @since Created in 2025-06-05
 */
@Service
@Slf4j
public class CommentServiceImpl implements CommentService {
    @Resource
    private SendMqRetryHelper sendMqRetryHelper;
    @Resource
    private DistributedIdGeneratorRpcService distributedIdGeneratorRpcService;
    @Resource
    private CommentDOMapper commentDOMapper;
    @Resource
    private NoteCountDOMapper noteCountDOMapper;
    @Resource
    private KeyValueRpcService keyValueRpcService;
    @Resource
    private UserRcpService userRcpService;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Resource(name = "taskExecutor")
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;
    @Resource
    private RocketMQTemplate rocketMQTemplate;
    @Resource
    private CommentLikeDOMapper commentLikeDOMapper;

    /**
     * 评论详情本地缓存
     */
    private static final Cache<Long, String> LOCAL_CACHE = Caffeine.newBuilder()
            .initialCapacity(10000)
            .maximumSize(10000)
            .expireAfterWrite(1, TimeUnit.HOURS)
            .build();

    @Resource
    private TransactionTemplate transactionTemplate;


    /**
     * 发表评论
     * @param publishCommentReqVO
     * @return
     */
    @Override
    public Response<?> publishComment(PublishCommentReqVO publishCommentReqVO) {
        String content = publishCommentReqVO.getContent();

        String imageUrl = publishCommentReqVO.getImageUrl();

        Preconditions.checkArgument(StringUtils.isNotBlank(content) || StringUtils.isNotBlank(imageUrl),
                "评论正文和图片不能同时为空");

        Long creatorId = LoginUserContextHolder.getUserId();

        String commentId = distributedIdGeneratorRpcService.generateCommentId();

        PublishCommentMqDTO publishCommentMqDTO = PublishCommentMqDTO.builder()
                .noteId(publishCommentReqVO.getNoteId())
                .content(content)
                .imageUrl(imageUrl)
                .replyCommentId(publishCommentReqVO.getReplyCommentId())
                .creatorId(creatorId)
                .commentId(Long.valueOf(commentId))
                .createTime(LocalDateTime.now())
                .build();

        sendMqRetryHelper.asyncSend(MQConstants.TOPIC_PUBLISH_COMMENT, JsonUtils.toJsonString(publishCommentMqDTO));
        return Response.success();
    }

    /**
     * 分页查询评论列表
     * @param findCommentListReqVO
     * @return
     */
    @Override
    public PageResponse<FindCommentItemRspVO> findCommentPageList(FindCommentListReqVO findCommentListReqVO) {
        Long noteId = findCommentListReqVO.getNoteId();
        Integer pageNo = findCommentListReqVO.getPageNo();
        int pageSize = 10;

        // 从缓存中间查询
        String noteCommentTotalKey = RedisKeyConstants.buildCountCommentTotalKey(noteId);
        Number commentTotal = (Number) redisTemplate.opsForHash()
                .get(noteCommentTotalKey, RedisKeyConstants.FIELD_COMMENT_TOTAL);
        long count = Objects.nonNull(commentTotal) ? commentTotal.longValue() : 0L;

        if (Objects.isNull(commentTotal)) {
            Long dbCount = noteCountDOMapper.selectCommentTotalByNoteId(noteId);
            if (Objects.isNull(dbCount)) {
                throw new BizException(ResponseCodeEnum.COMMENT_NOT_FOUND);
            }
            count = dbCount;

            // 同步 数据库数据到redis
            threadPoolTaskExecutor.execute(() -> {
                syncNoteCommentTotal2Redis(noteCommentTotalKey, dbCount);
            });
        }

        if (count == 0) {
            return PageResponse.success(null, pageNo, 0);
        }

        List<FindCommentItemRspVO> commentRspVOS = Lists.newArrayList();

        long offset = PageResponse.getOffset(pageNo, pageSize);

        String commentZSetKey = RedisKeyConstants.buildCommentListKey(noteId);
        Boolean hasKey = redisTemplate.hasKey(commentZSetKey);
        if (!hasKey) {
            // 异步将热点评论同步到 redis 中（最多同步 500 条）
            threadPoolTaskExecutor.execute(() -> {
                syncHeatComments2Redis(commentZSetKey, noteId);
            });
        }

        // 若 ZSET 缓存存在, 并且查询的是前 50 页的评论
        if (hasKey && offset < 500) {
             // 使用 ZRevRange 获取某篇笔记下，按热度降序排序的一级评论 ID
            Set<Object> commentIds = redisTemplate.opsForZSet()
                    .reverseRangeByScore(commentZSetKey, -Double.MAX_VALUE, Double.MAX_VALUE, offset, pageSize);

            if (CollUtil.isNotEmpty(commentIds)) {
                List<Object> commentIdList = Lists.newArrayList(commentIds);

                List<Long> localCacheExpiredCommentIds = Lists.newArrayList();

                List<Long> localCacheKeys = commentIdList.stream()
                        .map(commentId -> Long.valueOf(commentId.toString()))
                        .toList();

                // 批量查询本地缓存
                Map<Long, String> commentIdAndDetailJsonMap = LOCAL_CACHE.getAll(localCacheKeys, missingKeys -> {
                    Map<Long, String> missingData = Maps.newHashMap();
                    missingKeys.forEach(key -> {
                        localCacheExpiredCommentIds.add(key);
                        missingData.put(key, Strings.EMPTY);
                    });
                    return missingData;
                });

                // 若 localCacheExpiredCommentIds 的大小不等于 commentIdList 的大小，说明本地缓存中有数据
                if (CollUtil.size(localCacheExpiredCommentIds) != commentIdList.size()) {
                    for (String value : commentIdAndDetailJsonMap.values()) {
                        if (StringUtils.isBlank(value)) continue;

                        FindCommentItemRspVO commentRspVO = JsonUtils.parseObject(value, FindCommentItemRspVO.class);
                        commentRspVOS.add(commentRspVO);
                    }
                }

                 // 若 localCacheExpiredCommentIds 大小等于 0，说明评论详情数据都在本地缓存中，直接响应返参
                if (CollUtil.size(localCacheExpiredCommentIds) == 0) {
                    if (CollUtil.isNotEmpty(commentRspVOS)) {
                        setCommentCountData(commentRspVOS, localCacheExpiredCommentIds);
                    }

                    return PageResponse.success(commentRspVOS, pageNo, count, pageSize);
                }

                List<String> commentIdKeys = localCacheExpiredCommentIds.stream()
                        .map(RedisKeyConstants::buildCommentDetailKey)
                        .toList();

                List<Object> commentsJsonList = redisTemplate.opsForValue().multiGet(commentIdKeys);
                List<Long> expiredCommentIds = Lists.newArrayList();
                for (int i = 0; i < commentsJsonList.size(); i++) {
                    String commentJson = (String) commentsJsonList.get(i);
                    if (Objects.nonNull(commentJson)) {
                        FindCommentItemRspVO commentRspVO = JsonUtils.parseObject(commentJson, FindCommentItemRspVO.class);
                        commentRspVOS.add(commentRspVO);
                    } else {
                        expiredCommentIds.add(Long.valueOf(commentIdList.get(i).toString()));
                    }
                }

                // 对于缓存中存在的评论详情, 需要再次查询其计数数据
                if (CollUtil.isNotEmpty(commentRspVOS)) {
                    setCommentCountData(commentRspVOS, expiredCommentIds);
                }

                if (CollUtil.isNotEmpty(expiredCommentIds)) {
                    List<CommentDO> commentDOS = commentDOMapper.selectByCommentIds(expiredCommentIds);
                    getCommentDataAndSync2Redis(commentDOS, noteId, commentRspVOS);
                }
            }

            // 按热度值进行降序排列
            commentRspVOS = commentRspVOS.stream()
                    .sorted(Comparator.comparing(FindCommentItemRspVO::getHeat).reversed())
                    .collect(Collectors.toList());

            syncCommentDetail2LocalCache(commentRspVOS);

            return PageResponse.success(commentRspVOS, pageNo, count, pageSize);
        }

        // 缓存中没有，则查询数据库
        // 查询一级评论
        List<CommentDO> oneLevelCommentDOS = commentDOMapper.selectPageList(noteId, offset, pageSize);
        getCommentDataAndSync2Redis(oneLevelCommentDOS, noteId, commentRspVOS);

        syncCommentDetail2LocalCache(commentRspVOS);

        return PageResponse.success(commentRspVOS, pageNo, count, pageSize);
    }

    /**
     * 二级评论分页查询
     * @param findChildCommentPageListReqVO
     * @return
     */
    @Override
    public PageResponse<FindChildCommentItemRspVO> findChildCommentPageList(FindChildCommentPageListReqVO findChildCommentPageListReqVO) {
        Long parentCommentId = findChildCommentPageListReqVO.getParentCommentId();
        Integer pageNo = findChildCommentPageListReqVO.getPageNo();
        long pageSize = 6;

        //  从缓存中间查询数据
        String countCommentKey = RedisKeyConstants.buildCommentCountKey(parentCommentId);

        Number redisCount = (Number) redisTemplate.opsForHash()
                .get(countCommentKey, RedisKeyConstants.FIELD_CHILD_COMMENT_TOTAL);

        long count = Objects.nonNull(redisCount) ? 0L : redisCount.longValue();

        if (Objects.nonNull(redisCount)) {
            Long dbCount = commentDOMapper.selectChildCommentTotalById(parentCommentId);

            if (Objects.isNull(dbCount)) {
                throw new BizException(ResponseCodeEnum.PARENT_COMMENT_NOT_FOUND);
            }

            count = dbCount;

            // 异步将子评论总数同步到 Redis 中
            threadPoolTaskExecutor.execute(() -> {
                syncCommentCount2Redis(countCommentKey, dbCount);
            });

            if (count == 0) {
                return PageResponse.success(null, pageNo, 0);
            }
        }

        Long total = commentDOMapper.selectChildCommentTotalById(parentCommentId);

        if (Objects.isNull(total) || total == 0) {
            return PageResponse.success(null, pageNo, 0);
        }

        List<FindChildCommentItemRspVO> childCommentRspVOS = Lists.newArrayList();

        // 计算分页查询的偏移量 offset (需要 +1，因为最早回复的二级评论已经被展示了)
        long offset = PageResponse.getOffset(pageNo, pageSize) + 1;

        String commentListZSetKey = RedisKeyConstants.buildChildCommentListKey(parentCommentId);

        Boolean hasKey = redisTemplate.hasKey(commentListZSetKey);

        // 异步将子评论同步到 Redis 中（最多同步 6*10 条）
        if (!hasKey) {
            threadPoolTaskExecutor.execute(() -> {
                syncChildComments2Redis(parentCommentId, commentListZSetKey);
            });
        }

        // 若子评论 ZSET 缓存存在, 并且查询的是前 10 页的子评论
        if (hasKey && offset < 6*10) {
            Set<Object> childCommentIds = redisTemplate.opsForZSet()
                    .rangeByScore(commentListZSetKey, 0, Double.MAX_VALUE, offset, pageSize);

            if (CollUtil.isNotEmpty(childCommentIds)) {
                List<Object> childCommentIdList = Lists.newArrayList(childCommentIds);

                List<String> commentIdKeys = childCommentIds.stream()
                        .map(RedisKeyConstants::buildCommentDetailKey)
                        .toList();

                List<Object> commentsJsonList = redisTemplate.opsForValue().multiGet(commentIdKeys);

                // 缓存中间不存在数据 ID
                List<Long> expiredCommentIds = Lists.newArrayList();

                for (int i = 0; i < commentsJsonList.size(); i++) {
                    String commentJson = (String) commentsJsonList.get(i);
                    Long commentId = Long.valueOf(childCommentIdList.get(i).toString());

                    if (Objects.nonNull(commentJson)) {
                        FindChildCommentItemRspVO findChildCommentItemRspVO = JsonUtils.parseObject(commentJson, FindChildCommentItemRspVO.class);
                        childCommentRspVOS.add(findChildCommentItemRspVO);
                    } else {
                        expiredCommentIds.add(commentId);
                    }
                }

                // 对于缓存中存在的子评论, 需要再次查询 Hash, 获取其计数数据
                if (CollUtil.isNotEmpty(childCommentRspVOS)) {
                    setChildCommentCountData(childCommentRspVOS, expiredCommentIds);
                }

                // 对于不存在的子评论，需要批量从数据库中查询，并添加到 commentRspVOS 中
                if (CollUtil.isNotEmpty(expiredCommentIds)) {
                    List<CommentDO> commentDOS = commentDOMapper.selectByCommentIds(expiredCommentIds);
                    getChildCommentDataAndSync2Redis(commentDOS, childCommentRspVOS);
                }

                 childCommentRspVOS = childCommentRspVOS.stream()
                        .sorted(Comparator.comparing(FindChildCommentItemRspVO::getCommentId))
                        .collect(Collectors.toList());

                return PageResponse.success(childCommentRspVOS, pageNo, count, pageSize);
            }
        }

        List<CommentDO> childCommentDOS = commentDOMapper.selectChildPageList(parentCommentId, offset, pageSize);

        getChildCommentDataAndSync2Redis(childCommentDOS, childCommentRspVOS);

        return PageResponse.success(childCommentRspVOS, pageNo, total, pageSize);
    }

    /**
     * 点赞评论
     * @param likeCommentReqVO
     * @return
     */
    @Override
    public Response<?> likeComment(LikeCommentReqVO likeCommentReqVO) {

        Long commentId = likeCommentReqVO.getCommentId();
        // 1. 校验被点赞的评论是否存在
        checkCommentIsExist(commentId);

        // 2. 判断目标评论，是否已经被点赞
        Long userId = LoginUserContextHolder.getUserId();
        String bloomCommentLikesKey = RedisKeyConstants.buildBloomCommentLikesKey(userId);

        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/bloom_comment_like_check.lua")));
        script.setResultType(Long.class);

        Long result = redisTemplate.execute(script, Collections.singletonList(bloomCommentLikesKey), commentId);

        CommetLikeLuaResultEnum commetLikeLuaResultEnum = CommetLikeLuaResultEnum.valueOf(result);

        if (Objects.isNull(commetLikeLuaResultEnum)) {
            throw new BizException(ResponseCodeEnum.PARAM_NOT_VALID);
        }

        switch (commetLikeLuaResultEnum) {
            case NOT_EXIST -> {
                int count = commentLikeDOMapper.selectCountByUserIdAndCommentId(userId, commentId);

                long expireSeconds = 60*60 + RandomUtil.randomInt(60*60);

                if (count > 0) {
                    // 异步初始化布隆过滤器
                    threadPoolTaskExecutor.submit(() ->
                            batchAddCommentLike2BloomAndExpire(userId, expireSeconds, bloomCommentLikesKey));

                    throw new BizException(ResponseCodeEnum.COMMENT_ALREADY_LIKED);
                }
                // 若目标评论未被点赞，查询当前用户是否有点赞其他评论，有则同步初始化布隆过滤器
                batchAddCommentLike2BloomAndExpire(userId, expireSeconds, bloomCommentLikesKey);

                // 添加点赞评论 ID 到布隆过滤器中间
                script.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/bloom_add_comment_like_and_expire.lua")));
                script.setResultType(Long.class);
                redisTemplate.execute(script, Collections.singletonList(bloomCommentLikesKey), commentId, expireSeconds);
            }
            // 目标评论已经被点赞 (可能存在误判，需要进一步确认)
            case COMMENT_LIKED -> {
                //查询数据库校验是否点赞
                int count = commentLikeDOMapper.selectCountByUserIdAndCommentId(userId, commentId);

                if (count > 0) {
                    throw new BizException(ResponseCodeEnum.COMMENT_ALREADY_LIKED);
                }
            }
        }

        //  3. 发送 MQ, 异步将评论点赞记录落库
        LikeUnlikeCommentMqDTO likeUnlikeCommentMqDTO = LikeUnlikeCommentMqDTO.builder()
                .userId(userId)
                .commentId(commentId)
                .type(LikeUnLikeCommentTypeEnum.LIKE.getCode())
                .createTime(LocalDateTime.now())
                .build();

        Message<String> message = MessageBuilder.withPayload(JsonUtils.toJsonString(likeUnlikeCommentMqDTO))
                .build();

        String destination = MQConstants.TOPIC_COMMENT_LIKE_OR_UNLIKE + ":" + MQConstants.TAG_LIKE;

        String hashKey = String.valueOf(userId);

        rocketMQTemplate.asyncSendOrderly(destination, message, hashKey, new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {
                log.info("==> 【评论点赞】MQ 发送成功，SendResult: {}", sendResult);
            }

            @Override
            public void onException(Throwable throwable) {
                log.error("==> 【评论点赞】MQ 发送异常: ", throwable);
            }
        });

        return Response.success();
    }

    /**
     * 取消点赞操作
     * @param unlikeCommentReqVO
     * @return
     */
    @Override
    public Response<?> unlikeComment(UnlikeCommentReqVO unlikeCommentReqVO) {
        Long commentId = unlikeCommentReqVO.getCommentId();
        // 判断是否存在对应的评论
        checkCommentIsExist(commentId);

        Long userId = LoginUserContextHolder.getUserId();

        String bloomCommentLikesKey = RedisKeyConstants.buildBloomCommentLikesKey(userId);

        DefaultRedisScript<Long> script = new DefaultRedisScript<>();

        script.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/bloom_comment_unlike_check.lua")));

        script.setResultType(Long.class);

        Long result = redisTemplate.execute(script, Collections.singletonList(bloomCommentLikesKey), commentId);

        CommentUnlikeLuaResultEnum commentUnlikeLuaResultEnum = CommentUnlikeLuaResultEnum.valueOf(result);

        if (Objects.isNull(commentUnlikeLuaResultEnum)) {
            throw new BizException(ResponseCodeEnum.PARAM_NOT_VALID);
        }

        switch (commentUnlikeLuaResultEnum) {
            case NOT_EXIST -> {
                threadPoolTaskExecutor.submit(() -> {
                    long expireTime = 60*60 + RandomUtil.randomInt(60*60);
                    batchAddCommentLike2BloomAndExpire(userId, expireTime, bloomCommentLikesKey);
                });

                int count = commentLikeDOMapper.selectCountByUserIdAndCommentId(userId, commentId);

                if (count == 0) throw new BizException(ResponseCodeEnum.COMMENT_NOT_LIKED);

            }
            case COMMENT_NOT_LIKED -> throw new BizException(ResponseCodeEnum.COMMENT_NOT_LIKED);
        }

        // MQ 发送信息同步数据库删除点赞
        LikeUnlikeCommentMqDTO likeUnlikeCommentMqDTO = LikeUnlikeCommentMqDTO.builder()
                .userId(userId)
                .commentId(commentId)
                .type(LikeUnLikeCommentTypeEnum.UNLIKE.getCode())
                .createTime(LocalDateTime.now())
                .build();

        Message<String> message = MessageBuilder.withPayload(JsonUtils.toJsonString(likeUnlikeCommentMqDTO))
                .build();

        String destination = MQConstants.TOPIC_COMMENT_LIKE_OR_UNLIKE + ":" + MQConstants.TAG_UNLIKE;

        String hashKey = String.valueOf(userId);

        rocketMQTemplate.asyncSendOrderly(destination, message, hashKey, new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {
                log.info("==> 【评论取消点赞】MQ 发送成功，SendResult: {}", sendResult);
            }

            @Override
            public void onException(Throwable throwable) {
                log.error("==> 【评论取消点赞】MQ 发送异常: ", throwable);
            }
        });
        return Response.success();
    }

    /**
     * 删除评论
     * @param deleteCommentReqVO
     * @return
     */
    @Override
    public Response<?> deleteComment(DeleteCommentReqVO deleteCommentReqVO) {
        Long commentId = deleteCommentReqVO.getCommentId();
        // 1. 校验评论是否存在
        CommentDO commentDO = commentDOMapper.selectByPrimaryKey(commentId);

        if (Objects.isNull(commentDO)) {
            throw new BizException(ResponseCodeEnum.COMMENT_NOT_FOUND);
        }
        // 2. 校验是否有权限删除
        Long userId = LoginUserContextHolder.getUserId();

        if (!Objects.equals(userId, commentDO.getUserId())) {
            throw new BizException(ResponseCodeEnum.COMMENT_CANT_OPERATE);
        }
        // 3. 物理删除评论、评论内容
        transactionTemplate.execute(status -> {
            try {
                commentDOMapper.deleteByPrimaryKey(commentId);

                keyValueRpcService.deleteCommentContent(commentDO.getNoteId(),
                        commentDO.getCreateTime().toString(),
                        commentDO.getContentUuid());

                return null;
            } catch (Exception e) {
                status.setRollbackOnly();
                log.error("", e);
                throw e;
            }
        });
        // 4. 删除 Redis 缓存（ZSet 和 String）
        Integer level = commentDO.getLevel();
        Long noteId = commentDO.getNoteId();
        Long parentId = commentDO.getParentId();

        String redisZSetKey = Objects.equals(level, 1) ?
                RedisKeyConstants.buildCommentListKey(noteId) : RedisKeyConstants.buildChildCommentListKey(noteId);

        redisTemplate.executePipelined(new SessionCallback<>() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                operations.opsForZSet().remove(redisZSetKey ,commentId);

                operations.delete(RedisKeyConstants.buildCommentDetailKey(commentId));
                return null;
            }
        });
        // 5. 发布广播 MQ, 将本地缓存删除
        rocketMQTemplate.asyncSend(MQConstants.TOPIC_DELETE_COMMENT_LOCAL_CACHE, commentId, new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {
                log.info("==> 【删除评论详情本地缓存】MQ 发送成功，SendResult: {}", sendResult);
            }

            @Override
            public void onException(Throwable throwable) {
                log.error("==> 【删除评论详情本地缓存】MQ 发送异常: ", throwable);
            }
        });
        // 6. 发送 MQ, 异步去更新计数、删除关联评论、热度值等
        Message<String> message = MessageBuilder.withPayload(JsonUtils.toJsonString(commentDO))
                .build();

        rocketMQTemplate.asyncSend(MQConstants.TOPIC_DELETE_COMMENT, message, new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {
                log.info("==> 【评论删除】MQ 发送成功，SendResult: {}", sendResult);
            }

            @Override
            public void onException(Throwable throwable) {
                log.error("==> 【评论删除】MQ 发送异常: ", throwable);
            }
        });
        return Response.success();
    }

    /**
     * 删除本地缓存
     * @param commentId
     */
    @Override
    public void deleteCommentLocalCache(Long commentId) {
        LOCAL_CACHE.invalidate(commentId);
    }

    /**
     * 初始化评论点赞布隆过滤器
     * @param userId
     * @param expireSeconds
     * @param bloomCommentLikesKey
     */
    private void batchAddCommentLike2BloomAndExpire(Long userId, long expireSeconds, String bloomCommentLikesKey) {
        try{
            List<CommentLikeDO> commentLikeDOS = commentLikeDOMapper.selectByUserId(userId);

            if (CollUtil.isNotEmpty(commentLikeDOS)) {
                DefaultRedisScript<Long> script = new DefaultRedisScript<>();
                script.setResultType(Long.class);
                script.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/bloom_batch_add_comment_like_and_expire.lua")));

                List<Object> luaArgs = Lists.newArrayList();
                commentLikeDOS.forEach(commentLikeDO -> {
                    luaArgs.add(commentLikeDO.getCommentId());
                });
                luaArgs.add(expireSeconds);
                redisTemplate.execute(script, Collections.singletonList(bloomCommentLikesKey), luaArgs.toArray());
            }
        } catch (Exception e) {
            log.error("## 异步初始化【评论点赞】布隆过滤器异常: ", e);
        }
    }

    /**
     * 校验点赞评论是否存在
     * @param commentId
     */
    private void checkCommentIsExist(Long commentId) {
        String localCacheJson = LOCAL_CACHE.getIfPresent(commentId);

        if (StringUtils.isBlank(localCacheJson)) {
            String commentDetailKey = RedisKeyConstants.buildCommentDetailKey(commentId);

            Boolean hasKey = redisTemplate.hasKey(commentDetailKey);

            if (!hasKey) {
                CommentDO commentDO = commentDOMapper.selectByPrimaryKey(commentId);

                if (Objects.isNull(commentDO)) {
                    throw new BizException(ResponseCodeEnum.COMMENT_NOT_FOUND);
                }
            }
        }

    }

    /**
     * 查询 hash-table 中间存在的数据添加到 RSPvoS中间
     * @param commentRspVOS
     * @param expiredCommentIds
     */
    private void setChildCommentCountData(List<FindChildCommentItemRspVO> commentRspVOS, List<Long> expiredCommentIds) {
        List<Long> notExpiredCommentIds = Lists.newArrayList();

        commentRspVOS.forEach(commentRspVO -> {
            Long commentId = commentRspVO.getCommentId();
            notExpiredCommentIds.add(commentId);
        });

        Map<Long, Map<Object, Object>> commentIdAndCountMap =  getCommentCountDataAndSync2RedisHash(notExpiredCommentIds);
    }

    /**
     * 同步 hash
     * @param notExpiredCommentIds
     * @return
     */
    private Map<Long, Map<Object, Object>> getCommentCountDataAndSync2RedisHash(List<Long> notExpiredCommentIds) {
        List<Long> expiredCountCommentIds = Lists.newArrayList();

        List<String> commentCountKeys = notExpiredCommentIds.stream()
                .map(RedisKeyConstants::buildCountCommentTotalKey).toList();

        List<Object> results = redisTemplate.executePipelined(new SessionCallback<>() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                commentCountKeys.forEach(key ->
                        operations.opsForHash().entries(key));
                return null;
            }
        });

        Map<Long, Map<Object, Object>> commentIdAndCountMap = Maps.newHashMap();
        for (int i = 0; i < notExpiredCommentIds.size(); i++) {
            Long currCommentId = Long.valueOf(notExpiredCommentIds.get(i).toString());
            Map<Object, Object> hash = (Map<Object, Object>) results.get(i);

            if (CollUtil.isNotEmpty(hash)) {
                expiredCountCommentIds.add(currCommentId);
                continue;
            }
            commentIdAndCountMap.put(currCommentId, hash);
        }

        if (CollUtil.size(expiredCountCommentIds) > 0) {
            List<CommentDO> commentDOS = commentDOMapper.selectCommentCountByIds(expiredCountCommentIds);
            commentDOS.forEach(commentDO ->  {
                Integer level = commentDO.getLevel();
                Map<Object, Object> map = Maps.newHashMap();

                map.put(RedisKeyConstants.FIELD_LIKE_TOTAL, commentDO.getLikeTotal());

                if (Objects.equals(level, CommentLevelEnum.ONE.getCode())) {
                    map.put(RedisKeyConstants.FIELD_CHILD_COMMENT_TOTAL, commentDO.getChildCommentTotal());
                }
                commentIdAndCountMap.put(commentDO.getId(), map);
            });

            threadPoolTaskExecutor.execute(() -> {
                redisTemplate.executePipelined(new SessionCallback<>() {
                    @Override
                    public  Object execute(RedisOperations operations) throws DataAccessException {
                        commentDOS.forEach(commentDO -> {
                            String key = RedisKeyConstants.buildCommentCountKey(commentDO.getId());
                            Integer level = commentDO.getLevel();
                            Map<String, Long> fieldMap = Objects.equals(level, CommentLevelEnum.ONE.getCode()) ?
                                    Map.of(RedisKeyConstants.FIELD_CHILD_COMMENT_TOTAL, commentDO.getChildCommentTotal(),
                                            RedisKeyConstants.FIELD_LIKE_TOTAL, commentDO.getLikeTotal()) :Map.of(RedisKeyConstants.FIELD_CHILD_COMMENT_TOTAL, commentDO.getLikeTotal());

                            operations.opsForHash().putAll(key, fieldMap);

                            long expireTime = 60*60 + RandomUtil.randomInt(4 * 60 * 60);
                            operations.expire(key, expireTime, TimeUnit.SECONDS);
                        });
                        return null;
                    }
                });
            });

            return commentIdAndCountMap;
        }

        return null;
    }


    /**
     * 获取子评论并同步到 redis 中间
     * @param childCommentDOS
     * @param childCommentRspVOS
     */
    private void getChildCommentDataAndSync2Redis(List<CommentDO> childCommentDOS, List<FindChildCommentItemRspVO> childCommentRspVOS) {
        List<FindCommentContentReqDTO> findCommentContentReqDTOS = Lists.newArrayList();

        Set<Long> userIds = Sets.newHashSet();

        Long noteId = null;

        for (CommentDO childCommentDO : childCommentDOS) {
            noteId = childCommentDO.getNoteId();
            // KV 服务参数调用
            Boolean isContentEmpty = childCommentDO.getIsContentEmpty();
            if (!isContentEmpty) {
                FindCommentContentReqDTO findCommentContentReqDTO = FindCommentContentReqDTO.builder()
                        .contentId(childCommentDO.getContentUuid())
                        .yearMonth(DateConstants.DATE_FORMAT_Y_M.format(childCommentDO.getCreateTime()))
                        .build();

                findCommentContentReqDTOS.add(findCommentContentReqDTO);
            }
            userIds.add(childCommentDO.getUserId());

            Long parentId = childCommentDO.getParentId();
            Long replyCommentId = childCommentDO.getReplyCommentId();
            if (Objects.equals(parentId, replyCommentId)) {
                userIds.add(childCommentDO.getReplyUserId());
            }
        }

        List<FindCommentContentRspDTO> findCommentContentRspDTOS
                = keyValueRpcService.batchFindCommentContent(noteId, findCommentContentReqDTOS);

        Map<String, String> commentUuidAndContentMap = null;
        if (CollUtil.isNotEmpty(findCommentContentRspDTOS)) {
            commentUuidAndContentMap = findCommentContentRspDTOS.stream()
                    .collect(Collectors.toMap(FindCommentContentRspDTO::getContentId, FindCommentContentRspDTO::getContent));
        }

        List<FindUserByIdRspDTO> findUserByIdRspDTOS = userRcpService.findByIds(userIds.stream().toList());

        Map<Long, FindUserByIdRspDTO> userIdAndDTOMap = null;
        if (CollUtil.isNotEmpty(findUserByIdRspDTOS)) {
            userIdAndDTOMap = findUserByIdRspDTOS.stream()
                    .collect(Collectors.toMap(FindUserByIdRspDTO::getId, dto -> dto));
        }

        for (CommentDO childCommentDO : childCommentDOS) {
            Long userId = childCommentDO.getUserId();
            FindChildCommentItemRspVO childCommentRspVO = FindChildCommentItemRspVO.builder()
                    .userId(userId)
                    .commentId(childCommentDO.getId())
                    .imageUrl(childCommentDO.getImageUrl())
                    .createTime(DateUtils.formatRelativeTime(childCommentDO.getCreateTime()))
                    .likeTotal(childCommentDO.getLikeTotal())
                    .build();

            if (CollUtil.isNotEmpty(userIdAndDTOMap)) {
                FindUserByIdRspDTO findUserByIdRspDTO = userIdAndDTOMap.get(userId);
                if (Objects.nonNull(findUserByIdRspDTO)) {
                    childCommentRspVO.setAvatar(findUserByIdRspDTO.getAvatar());
                    childCommentRspVO.setNickname(findUserByIdRspDTO.getNickName());
                }
                Long replyCommentId = childCommentDO.getReplyCommentId();
                Long parentId = childCommentDO.getParentId();

                if (Objects.nonNull(replyCommentId)
                        && !Objects.equals(replyCommentId, parentId)) {
                    Long replyUserId = childCommentDO.getReplyUserId();
                    FindUserByIdRspDTO replyUser = userIdAndDTOMap.get(replyUserId);
                    childCommentRspVO.setReplyUserId(replyUser.getId());
                    childCommentRspVO.setReplyUserName(replyUser.getNickName());
                }

                if (CollUtil.isNotEmpty(commentUuidAndContentMap)) {
                    String contentUuid = childCommentDO.getContentUuid();
                    if (StringUtils.isNotBlank(contentUuid)) {
                        childCommentRspVO.setContent(commentUuidAndContentMap.get(contentUuid));
                    }
                }

                childCommentRspVOS.add(childCommentRspVO);
            }

            // 将反参数据批量添加到 redis 中间
            threadPoolTaskExecutor.execute(() -> {
                Map<String, String> data = Maps.newHashMap();
                childCommentRspVOS.forEach(commentRspDO -> {
                    Long commentId = commentRspDO.getCommentId();
                    String key = RedisKeyConstants.buildCommentDetailKey(commentId);

                    data.put(key, JsonUtils.toJsonString(commentRspDO));
                });

                batchAddCommentDetailJson2Redis(data);
            });
        }
    }

    /**
     * 批量添加评论数据到 redis 中间
     * @param data
     */
    private void batchAddCommentDetailJson2Redis(Map<String, String> data) {
        redisTemplate.executePipelined((RedisCallback<?>) connection -> {
            for (Map.Entry<String, String> entry : data.entrySet()) {
                String jsonString = JsonUtils.toJsonString(entry.getValue());

                int randomExpire = 60*60 + RandomUtil.randomInt(4 * 60 * 60);

                connection.setEx(
                        redisTemplate.getStringSerializer().serialize(entry.getKey()),
                        randomExpire,
                        redisTemplate.getStringSerializer().serialize(jsonString)
                );
            }

            return null;
        });
    }

    /**
     *  异步将子评论同步到 Redis 中（最多同步 6*10 条）
     * @param parentCommentId
     * @param commentListZSetKey
     */
    private void syncChildComments2Redis(Long parentCommentId, String commentListZSetKey) {
        List<CommentDO> childCommentDOS = commentDOMapper.selectChildCommentsByParentIdAndLimit(parentCommentId, 6 * 10);

        if (CollUtil.isNotEmpty(childCommentDOS)) {
            redisTemplate.executePipelined((RedisCallback<Object>) connection ->  {
                ZSetOperations<String, Object> opsForZSet = redisTemplate.opsForZSet();

                for (CommentDO childCommentDO : childCommentDOS) {
                    Long commentDOId = childCommentDO.getId();
                    long commentTimestamp = DateUtils.localDateTime2Timestamp(childCommentDO.getCreateTime());
                    opsForZSet.add(commentListZSetKey, commentDOId, commentTimestamp);
                }

                int randomExpiryTime = 60 * 60 + RandomUtil.randomInt(4 * 60 * 60);
                redisTemplate.expire(commentListZSetKey, randomExpiryTime, TimeUnit.SECONDS);

                return null;
            });
        }
    }

    /**
     * 异步将子评论总数同步到 Redis 中
     * @param countCommentKey
     * @param dbCount
     */
    private void syncCommentCount2Redis(String countCommentKey, Long dbCount) {
        redisTemplate.executePipelined(new SessionCallback<>() {
            @Override
            public  Object execute(RedisOperations operations) throws DataAccessException {
                operations.opsForHash()
                        .put(countCommentKey, RedisKeyConstants.FIELD_CHILD_COMMENT_TOTAL, dbCount);

                long expireTime = 60*60 + RandomUtil.randomInt(4 * 60 * 60);
                operations.expire(countCommentKey, expireTime, TimeUnit.SECONDS);

                return null;
            }
        });
    }

    /**
     * 同步评论详情到本地缓存中
     * @param commentRspVOS
     */
    private void syncCommentDetail2LocalCache(List<FindCommentItemRspVO> commentRspVOS) {
        threadPoolTaskExecutor.execute(() -> {
            Map<Long, String> localCacheData = Maps.newHashMap();

            commentRspVOS.forEach(commentRspVO -> {
                Long commentId = commentRspVO.getCommentId();
                localCacheData.put(commentId, JsonUtils.toJsonString(commentRspVO));
            });

            LOCAL_CACHE.putAll(localCacheData);
        });
    }

    /**
     * 获取全部评论数据，并将评论详情同步到 redis 中
     * @param oneLevelCommentDOS
     * @param noteId
     * @param commentRspVOS
     */
    private void getCommentDataAndSync2Redis(List<CommentDO> oneLevelCommentDOS, Long noteId, List<FindCommentItemRspVO> commentRspVOS) {
        List<Long> twoLevelCommentIds = oneLevelCommentDOS.stream()
                .map(CommentDO::getFirstReplyCommentId)
                .filter(firstReplyCommentId -> firstReplyCommentId != 0)
                .toList();

        Map<Long, CommentDO> commentIdAndDOMap = null;
        List<CommentDO> twoLevelCommentDOS = null;

        if (CollUtil.isNotEmpty(twoLevelCommentIds)) {
            twoLevelCommentDOS = commentDOMapper.selectTwoLevelCommentByIds(twoLevelCommentIds);

            commentIdAndDOMap = twoLevelCommentDOS.stream()
                    .collect(Collectors.toMap(CommentDO::getId, commentDO -> commentDO));
        }
        List<FindCommentContentReqDTO> findCommentContentReqDTOS = Lists.newArrayList();
        List<Long> userIds = Lists.newArrayList();

        List<CommentDO> allCommentDOS = Lists.newArrayList();
        CollUtil.addAll(allCommentDOS, oneLevelCommentDOS);
        CollUtil.addAll(allCommentDOS, twoLevelCommentDOS);

        allCommentDOS.forEach(commentDO -> {
            Boolean isContentEmpty = commentDO.getIsContentEmpty();
            if (!isContentEmpty) {
                FindCommentContentReqDTO findCommentContentReqDTO = FindCommentContentReqDTO.builder()
                        .contentId(commentDO.getContentUuid())
                        .yearMonth(DateConstants.DATE_FORMAT_Y_M.format(commentDO.getCreateTime()))
                        .build();

                findCommentContentReqDTOS.add(findCommentContentReqDTO);
            }
            userIds.add(commentDO.getId());
        });

        List<FindCommentContentRspDTO> findCommentContentRspDTOS =
                keyValueRpcService.batchFindCommentContent(noteId, findCommentContentReqDTOS);


        Map<String, String> commentUuidAndContentMap = null;
        if (CollUtil.isNotEmpty(findCommentContentRspDTOS)) {
            commentUuidAndContentMap = findCommentContentRspDTOS.stream()
                    .collect(Collectors.toMap(FindCommentContentRspDTO::getContentId, FindCommentContentRspDTO::getContent));
        }

        List<FindUserByIdRspDTO> findUserByIdRspDTOS = userRcpService.findByIds(userIds);
        Map<Long, FindUserByIdRspDTO> userIdAndDTOMap = null;
        if (CollUtil.isNotEmpty(findUserByIdRspDTOS)) {
            userIdAndDTOMap = findUserByIdRspDTOS.stream()
                    .collect(Collectors.toMap(FindUserByIdRspDTO::getId, dto -> dto));
        }

        for (CommentDO commentDO : oneLevelCommentDOS) {
            Long userId = commentDO.getUserId();
            FindCommentItemRspVO oneLevelCommentRspVO  = FindCommentItemRspVO.builder()
                    .userId(userId)
                    .commentId(commentDO.getId())
                    .imageUrl(commentDO.getImageUrl())
                    .createTime(DateUtils.formatRelativeTime(commentDO.getCreateTime()))
                    .likeTotal(commentDO.getLikeTotal())
                    .childCommentTotal(commentDO.getChildCommentTotal())
                    .heat(commentDO.getHeat())
                    .build();

            setUserInfo(commentIdAndDOMap, userIdAndDTOMap, userId, oneLevelCommentRspVO);
            setCommentContent(commentUuidAndContentMap, commentDO, oneLevelCommentRspVO);

            Long firstReplyCommentId = commentDO.getFirstReplyCommentId();
            if (CollUtil.isNotEmpty(commentIdAndDOMap)) {
                CommentDO firstReplyCommentDO = commentIdAndDOMap.get(firstReplyCommentId);
                if (Objects.nonNull(firstReplyCommentDO)) {
                    Long firstReplyCommentUserId = firstReplyCommentDO.getUserId();
                    FindCommentItemRspVO firstReplyCommentRspVO = FindCommentItemRspVO.builder()
                            .userId(firstReplyCommentDO.getUserId())
                            .commentId(firstReplyCommentDO.getId())
                            .imageUrl(firstReplyCommentDO.getImageUrl())
                            .createTime(DateUtils.formatRelativeTime(firstReplyCommentDO.getCreateTime()))
                            .likeTotal(firstReplyCommentDO.getLikeTotal())
                            .heat(firstReplyCommentDO.getHeat())
                                .build();

                    setUserInfo(commentIdAndDOMap, userIdAndDTOMap, firstReplyCommentUserId, firstReplyCommentRspVO);
                    // 用户信息
                    oneLevelCommentRspVO.setFirstReplyComment(firstReplyCommentRspVO);
                    // 笔记内容
                    setCommentContent(commentUuidAndContentMap, firstReplyCommentDO, firstReplyCommentRspVO);
                }
            }
            commentRspVOS.add(oneLevelCommentRspVO);
        }
        // 同步数据到 redis
        threadPoolTaskExecutor.execute(() -> {
            Map<String, String> data = Maps.newHashMap();
            commentRspVOS.forEach(commentRspVO -> {
                Long commentId = commentRspVO.getCommentId();
                String commentDetailKey = RedisKeyConstants.buildCommentDetailKey(commentId);
                data.put(commentDetailKey, JsonUtils.toJsonString(commentRspVO));
            });
            // 使用 executePipelined 实现性能插入数据
            batchAddCommentDetailJson2Redis(data);
        });

    }

    /**
     * 同步热点评论到 redis
     * @param commentZSetKey
     * @param noteId
     */
    private void syncHeatComments2Redis(String commentZSetKey, Long noteId) {
        List<CommentDO> commentDOS = commentDOMapper.selectHeatComments(noteId);
        if (CollUtil.isNotEmpty(commentDOS)) {
            redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
                ZSetOperations<String, Object> zSetOps = redisTemplate.opsForZSet();

                for (CommentDO commentDO : commentDOS) {
                    Long commentId = commentDO.getId();
                    Double commentHeat = commentDO.getHeat();
                    zSetOps.add(commentZSetKey, commentId, commentHeat);
                }

                int randomExpiryTime = RandomUtil.randomInt(5 * 60 * 60);
                redisTemplate.expire(commentZSetKey, randomExpiryTime, TimeUnit.SECONDS);
                return null;
            });
        }
    }

    /**
     * 同步笔记评论总数到 redis 中间
     * @param noteCommentTotalKey
     * @param dbCount
     */
    private void syncNoteCommentTotal2Redis(String noteCommentTotalKey, Long dbCount) {
        redisTemplate.executePipelined(new SessionCallback<>() {
            @Override
            public  Object execute(RedisOperations operations) throws DataAccessException {
                operations.opsForHash()
                        .put(noteCommentTotalKey, RedisKeyConstants.FIELD_COMMENT_TOTAL, dbCount);

                long expireTime = 60*60 + RandomUtil.randomInt(4*60*60);
                operations.expire(noteCommentTotalKey, expireTime, TimeUnit.SECONDS);
                return null;
            }
        });
    }

    /**
     * 设置评论内容
     * @param commentUuidAndContentMap
     * @param commentDO
     * @param firstReplyCommentRspVO
     */
    private void setCommentContent(Map<String, String> commentUuidAndContentMap, CommentDO commentDO, FindCommentItemRspVO firstReplyCommentRspVO) {
        if (CollUtil.isNotEmpty(commentUuidAndContentMap)) {
            String contentUuid = commentDO.getContentUuid();
            if (StringUtils.isNotBlank(contentUuid)) {
                firstReplyCommentRspVO.setContent(commentUuidAndContentMap.get(contentUuid));
            }
        }
    }

    /**
     * 设置用户信息
     * @param commentIdAndDOMap
     * @param userIdAndDTOMap
     * @param userId
     * @param oneLevelCommentDOS
     */
    private void setUserInfo(Map<Long, CommentDO> commentIdAndDOMap, Map<Long, FindUserByIdRspDTO> userIdAndDTOMap, Long userId, FindCommentItemRspVO oneLevelCommentDOS) {
        FindUserByIdRspDTO findUserByIdRspDTO = userIdAndDTOMap.get(userId);
        if (Objects.nonNull(findUserByIdRspDTO)) {
            oneLevelCommentDOS.setAvatar(findUserByIdRspDTO.getAvatar());
            oneLevelCommentDOS.setNickname(findUserByIdRspDTO.getNickName());
        }
    }

    /**
     * 设计评论的计数
     * @param commentRspVOS
     * @param expiredCommentIds
     */
    private void setCommentCountData(List<FindCommentItemRspVO> commentRspVOS,
                                     List<Long> expiredCommentIds ) {

        List<Long> notExpiredCommentIds = Lists.newArrayList();

        commentRspVOS.forEach(commentRspVO -> {
            Long commentId = commentRspVO.getCommentId();
            notExpiredCommentIds.add(commentId);
            FindCommentItemRspVO firstReplyComment = commentRspVO.getFirstReplyComment();
            if (Objects.nonNull(firstReplyComment)) {
                notExpiredCommentIds.add(firstReplyComment.getCommentId());
            }
        });

        Map<Long, Map<Object, Object>> commentIdAndCountMap = getCommentCountDataAndSync2RedisHash(notExpiredCommentIds);

        for (FindCommentItemRspVO commentRspVO : commentRspVOS) {
            Long commentId = commentRspVO.getCommentId();

            if (CollUtil.isNotEmpty(expiredCommentIds)
                    && expiredCommentIds.contains(commentId)) {
                continue;
            }

            Map<Object, Object> hash = commentIdAndCountMap.get(commentId);
            if (CollUtil.isNotEmpty(hash)) {
                Object childCommentTotalObj = hash.get(RedisKeyConstants.FIELD_CHILD_COMMENT_TOTAL);
                Long childCommentTotal = Objects.isNull(childCommentTotalObj) ? 0 : Long.parseLong(childCommentTotalObj.toString());
                Object likeTotalObj = hash.get(RedisKeyConstants.FIELD_LIKE_TOTAL);
                Long likeTotal = Objects.isNull(likeTotalObj) ? 0 : Long.parseLong(likeTotalObj.toString());
                commentRspVO.setChildCommentTotal(childCommentTotal);
                commentRspVO.setLikeTotal(likeTotal);
                // 最初回复的二级评论
                FindCommentItemRspVO firstCommentVO = commentRspVO.getFirstReplyComment();
                if (Objects.nonNull(firstCommentVO)) {
                    Long firstCommentId = firstCommentVO.getCommentId();
                    Map<Object, Object> firstCommentHash = commentIdAndCountMap.get(firstCommentId);
                    if (CollUtil.isNotEmpty(firstCommentHash)) {
                        Long firstCommentLikeTotal = Long.valueOf(firstCommentHash.get(RedisKeyConstants.FIELD_LIKE_TOTAL).toString());
                        firstCommentVO.setLikeTotal(firstCommentLikeTotal);
                    }
                }
            }
        }
    }
}
